/*
Copyright 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import { fireEvent, render, screen, within } from "@testing-library/react";
import { defer, IDeferred } from "matrix-js-sdk/src/utils";

import EventIndexPanel from "../../../../src/components/views/settings/EventIndexPanel";
import EventIndexPeg from "../../../../src/indexing/EventIndexPeg";
import EventIndex from "../../../../src/indexing/EventIndex";
import { clearAllModals, flushPromises, getMockClientWithEventEmitter } from "../../../test-utils";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../../src/settings/SettingLevel";

describe("<EventIndexPanel />", () => {
    getMockClientWithEventEmitter({
        getRooms: jest.fn().mockReturnValue([]),
    });

    const getComponent = () => render(<EventIndexPanel />);

    beforeEach(() => {
        jest.spyOn(EventIndexPeg, "get").mockRestore();
        jest.spyOn(EventIndexPeg, "platformHasSupport").mockReturnValue(false);
        jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(false);
        jest.spyOn(EventIndexPeg, "initEventIndex").mockClear().mockResolvedValue(true);
        jest.spyOn(EventIndexPeg, "deleteEventIndex").mockClear();
        jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(false);
        jest.spyOn(SettingsStore, "setValue").mockClear();

        // @ts-ignore private property
        EventIndexPeg.error = null;
    });

    afterEach(async () => {
        await clearAllModals();
    });

    describe("when event index is initialised", () => {
        it("renders event index information", () => {
            jest.spyOn(EventIndexPeg, "get").mockReturnValue(new EventIndex());

            const { container } = getComponent();

            expect(container).toMatchSnapshot();
        });

        it("opens event index management dialog", async () => {
            jest.spyOn(EventIndexPeg, "get").mockReturnValue(new EventIndex());
            getComponent();

            fireEvent.click(screen.getByText("Manage"));

            const dialog = await screen.findByRole("dialog");
            expect(within(dialog).getByText("Message search")).toBeInTheDocument();

            // close the modal
            fireEvent.click(within(dialog).getByText("Done"));
        });
    });

    describe("when event indexing is fully supported and enabled but not initialised", () => {
        beforeEach(() => {
            jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);
            jest.spyOn(EventIndexPeg, "platformHasSupport").mockReturnValue(true);
            jest.spyOn(SettingsStore, "getValueAt").mockReturnValue(true);

            // @ts-ignore private property
            EventIndexPeg.error = new Error("Test error message");
        });

        it("displays an error when no event index is found and enabling not in progress", () => {
            getComponent();

            expect(screen.getByText("Message search initialisation failed")).toBeInTheDocument();
        });

        it("displays an error from the event index", () => {
            getComponent();

            expect(screen.getByText("Test error message")).toBeInTheDocument();
        });

        it("asks for confirmation when resetting seshat", async () => {
            getComponent();

            fireEvent.click(screen.getByText("Reset"));

            // wait for reset modal to open
            await screen.findByText("Reset event store?");
            const dialog = await screen.findByRole("dialog");

            expect(within(dialog).getByText("Reset event store?")).toBeInTheDocument();
            fireEvent.click(within(dialog).getByText("Cancel"));

            // didn't reset
            expect(SettingsStore.setValue).not.toHaveBeenCalled();
            expect(EventIndexPeg.deleteEventIndex).not.toHaveBeenCalled();
        });

        it("resets seshat", async () => {
            getComponent();

            fireEvent.click(screen.getByText("Reset"));

            // wait for reset modal to open
            await screen.findByText("Reset event store?");
            const dialog = await screen.findByRole("dialog");

            fireEvent.click(within(dialog).getByText("Reset event store"));

            await flushPromises();

            expect(SettingsStore.setValue).toHaveBeenCalledWith(
                "enableEventIndexing",
                null,
                SettingLevel.DEVICE,
                false,
            );
            expect(EventIndexPeg.deleteEventIndex).toHaveBeenCalled();

            await clearAllModals();
        });
    });

    describe("when event indexing is supported but not enabled", () => {
        it("renders enable text", () => {
            jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);

            getComponent();

            expect(
                screen.getByText("Securely cache encrypted messages locally for them to appear in search results."),
            ).toBeInTheDocument();
        });
        it("enables event indexing on enable button click", async () => {
            jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(true);
            let deferredInitEventIndex: IDeferred<boolean> | undefined;
            jest.spyOn(EventIndexPeg, "initEventIndex").mockImplementation(() => {
                deferredInitEventIndex = defer<boolean>();
                return deferredInitEventIndex.promise;
            });

            getComponent();

            fireEvent.click(screen.getByText("Enable"));

            await flushPromises();
            // spinner shown while enabling
            expect(screen.getByLabelText("Loading…")).toBeInTheDocument();

            // add an event indx to the peg and resolve the init promise
            jest.spyOn(EventIndexPeg, "get").mockReturnValue(new EventIndex());
            expect(EventIndexPeg.initEventIndex).toHaveBeenCalled();
            deferredInitEventIndex!.resolve(true);
            await flushPromises();
            expect(SettingsStore.setValue).toHaveBeenCalledWith("enableEventIndexing", null, SettingLevel.DEVICE, true);

            // message for enabled event index
            expect(
                screen.getByText(
                    "Securely cache encrypted messages locally for them to appear in search results, using 0 Bytes to store messages from 0 rooms.",
                ),
            ).toBeInTheDocument();
        });
    });

    describe("when event indexing is supported but not installed", () => {
        it("renders link to install seshat", () => {
            jest.spyOn(EventIndexPeg, "supportIsInstalled").mockReturnValue(false);
            jest.spyOn(EventIndexPeg, "platformHasSupport").mockReturnValue(true);

            const { container } = getComponent();

            expect(container).toMatchSnapshot();
        });
    });

    describe("when event indexing is not supported", () => {
        it("renders link to download a desktop client", () => {
            jest.spyOn(EventIndexPeg, "platformHasSupport").mockReturnValue(false);

            const { container } = getComponent();

            expect(container).toMatchSnapshot();
        });
    });
});
